/* Sentinel - IRC Statistical and Operator Services
** send.c - Functions to send data to sockets
**
** Copyright W. Campbell and others.  See README for more details
** Some code Copyright: Jonathan George, Kai Seidler, ircd-hybrid Team,
**                      IRCnet IRCD developers.
**
** $Id: send.c 7 2010-02-01 19:17:42Z dubkat $
*/

#include "stats.h"
#include "struct.h"
#include "proto.h"
#include "event.h"
#include "slog.h"
#include "mem.h"

static int sendto_dcc_nr(int, char *, ...);
static int vsendto_dcc(int, char *, va_list);

static void sendq_add(struct Connection *, char *, int, int);
static int sendq_flush(struct Connection *);

/* send a line of text to the server, Append the \n */
int sts(char *fmt, ...)
{
  va_list ap;
  char buf[BUFSIZE];
  int len, n;
  /* Still need this optimized XXX */
  struct Connection *conn = find_connection(servsock);

  va_start (ap, fmt);
  vsnprintf (buf, BUFSIZE, fmt, ap);

  if (servsock == -1)
  {
    slog(DEFAULT_LOG, L_WARNING, "LOST: %s", buf);
    return 1;
  }

  if (conn == NULL)
  {
    slog(DEFAULT_LOG, L_WARNING, "NULL Connection Struct for servsock %d",
         servsock);
    return 1;
  }

  debug("SENT: %s", buf);
#ifdef IO_LOG

  slog(IO_LOGNO, L_NOTICE, "S: %s", buf);
#endif

  strlcat(buf, "\r\n", BUFSIZE);

  len = strlen(buf);
  netinfo.outb += len;

  n = sendq_flush(conn);
  if (n == -1)
  {
    if (errno != EAGAIN)
    {
      slog(DEFAULT_LOG, L_WARNING, "Write error to server.");
      close(servsock);
      ServTermination();
      delete_connection(servsock);
      servsock = -1;
      return 1;
    }
    else
    {
      sendq_add(conn, buf, len, 0);
      return 0;
    }
  }
  else if (n == 0)
  {
    sendq_add(conn, buf, len, 0);
    return 0;
  }

  if ((n = write(servsock, buf, len)) == -1)
  {
    if (errno != EAGAIN)
    {
      slog(DEFAULT_LOG, L_WARNING, "Write error to server.");
      close(servsock);
      ServTermination();
      delete_connection(servsock);
      servsock = -1;
      return 1;
    }
    else
    {
      sendq_add(conn, buf, len, 0);
      return 0;
    }
  }

  if (n != len)
    sendq_add(conn, buf, len, n);

  va_end (ap);

  return 0;
}

int sendto_dcc(int fd, char *fmt, ...)
{
  va_list ap;
  char buf[BUFSIZE];
  struct Connection *conn;
  int len, n;

  conn = find_connection(fd);
  if (conn == NULL)
  {
    /* We've already moved this into the dying list (we hope), just return */
    return 1;
  }

  va_start (ap, fmt);
  vsnprintf (buf, BUFSIZE, fmt, ap);

  debug("DCC SENT: %s", buf);
#ifdef IO_LOG

  slog(IO_LOGNO, L_NOTICE, "DS: %s", buf);
#endif

  strlcat(buf, "\n", BUFSIZE);

  /* netinfo.outb += strlen(buf); */
  len = strlen(buf);

  n = sendq_flush(conn);
  if (n == -1)
  {
    if (errno != EAGAIN)
    {
      slog(DEFAULT_LOG, L_NOTICE, "Write error. Client %s (FD:%d) [%s]",
           conn->name, fd,
           (conn->sa != NULL) ? inet_ntoa(conn->sa->sin_addr) :
           "Unset");
      close(fd);
      conn->flags |= CONN_DEAD;
      if (!(conn->flags & CONN_CONNECTING))
        sendto_all_dcc("Client %s disconnected", conn->name);
      dying_connection(fd);
      return 1;
    }
    else
    {
      sendq_add(conn, buf, len, 0);
      return 0;
    }
  }
  else if (n == 0)
  {
    sendq_add(conn, buf, len, 0);
    return 0;
  }

  if ((n = write(fd, buf, len)) == -1)
  {
    if (errno != EAGAIN)
    {
      slog(DEFAULT_LOG, L_NOTICE, "Write error. Client %s (FD:%d) [%s]",
           conn->name, fd,
           (conn->sa != NULL) ? inet_ntoa(conn->sa->sin_addr) :
           "Unset");
      close(fd);
      conn->flags |= CONN_DEAD;
      if (!(conn->flags & CONN_CONNECTING))
        sendto_all_dcc("Client %s disconnected", conn->name);
      dying_connection(fd);
      return 1;
    }
    else
    {
      sendq_add(conn, buf, len, 0);
      return 0;
    }
  }

  if (n != len)
    sendq_add(conn, buf, len, n);

  va_end (ap);
  return 0;
}

int vsendto_dcc(int fd, char *fmt, va_list ap)
{
  char buf[BUFSIZE];
  struct Connection *conn;
  int len, n;

  conn = find_connection(fd);
  if (conn == NULL)
  {
    /* We've already moved this into the dying list (we hope), just return */
    return 1;
  }

  vsnprintf (buf, BUFSIZE, fmt, ap);

  debug("DCC SENT: %s", buf);
#ifdef IO_LOG

  slog(IO_LOGNO, L_NOTICE, "DS: %s", buf);
#endif

  strlcat(buf, "\n", BUFSIZE);

  /* netinfo.outb += strlen(buf); */
  len = strlen(buf);

  n = sendq_flush(conn);
  if (n == -1)
  {
    if (errno != EAGAIN)
    {
      slog(DEFAULT_LOG, L_NOTICE, "Write error. Client %s (FD:%d) [%s]",
           conn->name, fd,
           (conn->sa != NULL) ? inet_ntoa(conn->sa->sin_addr) :
           "Unset");
      close(fd);
      conn->flags |= CONN_DEAD;
      if (!(conn->flags & CONN_CONNECTING))
        sendto_all_dcc("Client %s disconnected", conn->name);
      dying_connection(fd);
      return 1;
    }
    else
    {
      sendq_add(conn, buf, len, 0);
      return 0;
    }
  }
  else if (n == 0)
  {
    sendq_add(conn, buf, len, 0);
    return 0;
  }

  if ((n = write(fd, buf, len)) == -1)
  {
    if (errno != EAGAIN)
    {
      slog(DEFAULT_LOG, L_NOTICE, "Write error. Client %s (FD:%d) [%s]",
           conn->name, fd,
           (conn->sa != NULL) ? inet_ntoa(conn->sa->sin_addr) :
           "Unset");
      close(fd);
      conn->flags |= CONN_DEAD;
      if (!(conn->flags & CONN_CONNECTING))
        sendto_all_dcc("Client %s disconnected", conn->name);
      dying_connection(fd);
      return 1;
    }
    else
    {
      sendq_add(conn, buf, len, 0);
      return 0;
    }
  }

  if (n != len)
    sendq_add(conn, buf, len, n);

  return 0;
}

/* Send to an fd without adding a terminating newline. */
static int sendto_dcc_nr(int fd, char *fmt, ...)
{
  va_list ap;
  char buf[BUFSIZE];
  struct Connection *conn;
  int len, n;

  conn = find_connection(fd);
  if (conn == NULL)
  {
    /* We've already moved this into the dying list (we hope), just return */
    return 1;
  }

  va_start (ap, fmt);
  vsnprintf (buf, BUFSIZE, fmt, ap);

  debug("DCC SENT: %s", buf);
#ifdef IO_LOG

  slog(IO_LOGNO, L_NOTICE, "DS: %s", buf);
#endif

  /* netinfo.outb += strlen(buf); */
  len = strlen(buf);

  n = sendq_flush(conn);
  if (n == -1)
  {
    if (errno != EAGAIN)
    {
      slog(DEFAULT_LOG, L_NOTICE, "Write error. Client %s (FD:%d) [%s]",
           conn->name, fd,
           (conn->sa != NULL) ? inet_ntoa(conn->sa->sin_addr) :
           "Unset");
      close(fd);
      conn->flags |= CONN_DEAD;
      if (!(conn->flags & CONN_CONNECTING))
        sendto_all_dcc("Client %s disconnected", conn->name);
      dying_connection(fd);
      return 1;
    }
    else
    {
      sendq_add(conn, buf, len, 0);
      return 0;
    }
  }
  else if (n == 0)
  {
    sendq_add(conn, buf, len, 0);
    return 0;
  }

  if ((n = write(fd, buf, len)) == -1)
  {
    if (errno != EAGAIN)
    {
      slog(DEFAULT_LOG, L_NOTICE, "Write error. Client %s (FD:%d) [%s]",
           conn->name, fd,
           (conn->sa != NULL) ? inet_ntoa(conn->sa->sin_addr) :
           "Unset");
      close(fd);
      conn->flags |= CONN_DEAD;
      if (!(conn->flags & CONN_CONNECTING))
        sendto_all_dcc("Client %s disconnected", conn->name);
      dying_connection(fd);
      return 1;
    }
    else
    {
      sendq_add(conn, buf, len, 0);
      return 0;
    }
  }

  if (n != len)
    sendq_add(conn, buf, len, n);

  va_end (ap);
  return 0;
}

void sendto_all_dcc(char *fmt, ...)
{
  va_list ap;
  dlink_node *dl;
  struct Connection *conn;

  va_start (ap, fmt);

  DLINK_FOREACH(dl, connection_list.head)
  {
    conn = (struct Connection *) dl->data;
    if (conn->flags & CONN_CONNECTING)
      continue;
    if (conn->flags & (CONN_DCCIN | CONN_DCCOUT))
    {
      if (conn->flags & CONN_DEAD)
        continue;
      vsendto_dcc(conn->fd, fmt, ap);
    }
  }

  va_end (ap);
}

void sendto_dcc_flag(int flag, char *fmt, ...)
{
  va_list ap;
  dlink_node *dl;
  struct Connection *conn;

  va_start (ap, fmt);

  DLINK_FOREACH(dl, connection_list.head)
  {
    conn = (struct Connection *) dl->data;
    /* Check for the flag first, save us some time later */
    if (!(conn->flags & flag))
      continue;
    if (conn->flags & CONN_CONNECTING)
      continue;
    if (conn->flags & (CONN_DCCIN | CONN_DCCOUT))
    {
      if (conn->flags & CONN_DEAD)
        continue;
      vsendto_dcc(conn->fd, fmt, ap);
    }
  }

  va_end(ap);
}

void sendto_partyline(struct Connection *sender, char *fmt, ...)
{
  va_list ap;
  dlink_node *dl;
  struct Connection *conn;

  va_start (ap, fmt);

  DLINK_FOREACH(dl, connection_list.head)
  {
    conn = (struct Connection *) dl->data;
    if (conn->flags & CONN_CONNECTING)
      continue;
    if (conn->flags & (CONN_DCCIN | CONN_DCCOUT))
    {
      if (conn->flags & CONN_DEAD)
        continue;
      if ((conn->flags & CONN_NOECHO) && (conn == sender))
        continue;
      if (conn->flags & CF_CHAT)
        vsendto_dcc(conn->fd, fmt, ap);
    }
  }

  va_end (ap);
}

/* Read a help file and send it to the recipient via DCC */
void send_dcc_help_file(int fd, char *filename, char *query)
{
  FILE *fp = fopen(filename, "r");
  char buffer[BUFSIZE];

  if (!fp)
  {
    sendto_dcc(fd, "No help available for: %s", query);
    slog(DEFAULT_LOG, L_ERR, "No help available for %s, filename %s", query,
         filename);
    return ;
  }

  while (fgets(buffer, BUFSIZE, fp))
  {
    if (buffer[0])
      sendto_dcc_nr(fd, "%s", buffer);
    else
      sendto_dcc(fd, " ");
  }

  fclose(fp);
}

static void sendq_add(struct Connection *conn, char *buf, int len, int pos)
{
  dlink_node *dl = dlink_create();
  struct sendq *sq = smalloc(sizeof(struct sendq));

  slog(DEFAULT_LOG, L_NOTICE, "sendq_add(%s) triggered", conn->name);

  memcounts.sendq++;

  sq->buf = sstrdup(buf);
  sq->len = len - pos;
  sq->pos = pos;
  dlink_add_tail(sq, dl, &conn->sendq);
}

/* Return 1 on successful flushing, 0 on a partial write (what is waiting
** to be written by the caller must be put into the sendQ), or -1 on an
** error, signalling the caller to destroy this socket.
*/
static int sendq_flush(struct Connection *conn)
{
  dlink_node *dl, *tdl;
  struct sendq *sq;
  int n;

  DLINK_FOREACH_SAFE(dl, tdl, conn->sendq.head)
  {
    sq = dl->data;

    /* Pointer arithmetic...write from where it stopped */
    if ((n = write(conn->fd, sq->buf + sq->pos, sq->len)) == -1)
    {
      if (errno != EAGAIN)
        return -1;
      return 0;
    }

    /* Wrote the full sendQ entry? */
    if (n == sq->len)
    {
      dlink_delete(dl, &conn->sendq);
      free(sq->buf);
      free(sq);
      dlink_free(dl);
      memcounts.sendq--;
    }
    else
    {
      sq->pos += n;
      sq->len -= n;
      return 0;
    }
  }

  return 1;
}

